// Local Includes
#include "stdafx.h"
#include "MyContext.h"

// SDL Includes
#include <sdl.h>
#include <SDL_opengl.h>

// System Includes
#include <math.h>

const GLenum _eLight[] = {
    GL_LIGHT1,
    GL_LIGHT2,
    GL_LIGHT3,
    GL_LIGHT4
};

const float _fLightColor[][4] = { 
    {1.0f, 1.0f, 1.0f, 1.0f},
    {0.0f, 1.0f, 1.0f, 1.0f},
    {1.0f, 1.0f, 0.0f, 1.0f},
    {1.0f, 0.0f, 0.0f, 1.0f}
};

const float _fZero[] = { 0.0f, 0.0f, 0.0f, 1.0f };

//                                 -1/sqrt(2)   -1/sqrt(2)
//const float _fSpotAim[] = { 0.0f, -0.707106781, -0.707106781};
//const float _fSpotAim[] = { 0.0f, -1.0f, -1.0f};

const float _fSpotAim[] = { 0.0f, 0.0f, -1.0f};
const float _fSpotCutoff = 90.0f;


// System-indepenant paths ( a lofty goal!)
#define ObtainPath( file ) \
{                           \
}

MyContext::MyContext()
: _texture(),
  _instrument(),
#ifndef __APPLE_CC__
  _glActiveTexture(NULL),
#endif
  _font()
{
}

MyContext::~MyContext()
{
}

bool 
MyContext::configure()
{
    bool res;
    
    // Load Screen Textures
    res = _texture[WINDOW_BACKGROUND_TEX].loadImage(".\\tex\\sand.tif");
    res = _texture[SPLASH_SCREEN].loadImage(".\\tex\\splash_screen.tif");
    res = _texture[MENU_TEX].loadImage(".\\tex\\menu.tif");
    
#ifndef __APPLE_CC__
    // Detected OpenGL Functionality
    _glActiveTexture = (PFNGLACTIVETEXTUREPROC)SDL_GL_GetProcAddress("glActiveTexture");
#endif
    float color[] = { 51.0f/255.0f, 0.0f, 0.0f, 1.0f };
    _font.init("Courier New", color );
    
    // initialize the lights
    glShadeModel(GL_SMOOTH);
    float fDiffuseL0[] = {0.4f, 0.4f, 0.4f, 1.0f};
    glLightfv(GL_LIGHT0, GL_DIFFUSE, fDiffuseL0);
    glEnable(GL_LIGHT0);
//    glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, 1.0f);
    for(int i =0; i < NUM_MOTES; i++) {
        glDisable(_eLight[i]);
        // color, direction, and cutoff of each light
        glLightfv(_eLight[i], GL_DIFFUSE, _fLightColor[i]);
        glLightfv(_eLight[i], GL_SPECULAR, _fZero);
        glLightfv(_eLight[i], GL_AMBIENT, _fZero);
        glLightfv(_eLight[i], GL_SPOT_DIRECTION, _fSpotAim);
        glLightf(_eLight[i], GL_SPOT_CUTOFF, _fSpotCutoff);
    }
    
    return true;
}


bool 
MyContext::renderText( char* pStr, int x, int y ) const
{
    return _font.render( (MyContext*)this, pStr, x, y );
}


bool
MyContext::renderSplash( MyTexObj id, unsigned int uiTime, 
                         float fCentX, float fCentY, 
                         float fTexW, float fTexH, 
                         bool bClear ) const
{
    unsigned int uiEnd = uiTime+ SDL_GetTicks();
    
    for(unsigned int ui = SDL_GetTicks(); ui < uiEnd; 
                     ui = SDL_GetTicks() ) {
                     
        if(bClear) {
            glClearColor( 0.0, 0.0, 0.0, 1.0 );
            glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        }
        
        MyImage * pImage = (MyImage*)texture(id);

        pImage->bindTexture();
        pImage->enableTexture();

        glBegin(GL_TRIANGLE_STRIP);  
            // Vertex 0
            glTexCoord2f(0.0f,0.0f);
            glVertex3f(fCentX-fTexW/2.0f,fCentY-fTexH/2.0f,0.0f);
            // Vertex 1
            glTexCoord2f(0.0f,1.0f);
            glVertex3f(fCentX-fTexW/2.0f,fCentY+fTexH/2.0f,0.0f);
            // Vertex 2
            glTexCoord2f(1.0f,0.0f);
            glVertex3f(fCentX+fTexW/2.0f,fCentY-fTexH/2.0f,0.0f);
            // Vertex 3
            glTexCoord2f(1.0f,1.0f);
            glVertex3f(fCentX+fTexW/2.0f,fCentY+fTexH/2.0f,0.0f);
        glEnd();
        
        pImage->disableTexture();

        SDL_GL_SwapBuffers( );
    }
    
    return true;
}


bool 
MyContext::renderLights(float fLight[][4], unsigned short usCount)
{
    glPushMatrix();
    glLoadIdentity();
    glRotatef(-30.0f, 1.0f, 0.0f, 0.0f);
    
    for(int i = 0; i < NUM_MOTES; ++i) {
        glDisable(_eLight[i]);
        glLightfv(_eLight[i], GL_POSITION, fLight[i]);
        glEnable(_eLight[i]);
    }
    
    glPopMatrix();
    return true;
}


bool
MyContext::renderRect( float x, float y, float w, float h ) const
{
    glBegin(GL_TRIANGLE_STRIP);
        // Vertex 0
        glNormal3f(0.0f, 0.0f, 1.0f);
        glTexCoord2f(0.0f,0.0f);
        glVertex3f(x,y,0.0f);
        // Vertex 1
        glNormal3f(0.0f, 0.0f, 1.0f);
        glTexCoord2f(0.0f,1.0f);
        glVertex3f(x,y+h,0.0f);
        // Vertex 2
        glNormal3f(0.0f, 0.0f, 1.0f);
        glTexCoord2f(1.0f,0.0f);
        glVertex3f(x+w,y,0.0f);
        // Vertex 3
        glNormal3f(0.0f, 0.0f, 1.0f);
        glTexCoord2f(1.0f,1.0f);
        glVertex3f(x+w,y+h,0.0f);
    glEnd();  

    return true;
}

bool
MyContext::renderTexRect( MyTexObj i, const float *fCol, 
                          float x, float y, 
                          float w, float h ) const
{
    glPushMatrix();
    glLoadIdentity();

    glColor4fv(fCol);

    MyImage *pImage = (MyImage*)texture(i);

    pImage->bindTexture();
    pImage->enableTexture();

    glBegin(GL_TRIANGLE_STRIP);
        // Vertex 0
        glNormal3f(0.0f, 0.0f, 1.0f);
        glTexCoord2f(0.0f,0.0f);
        glVertex3f(x,y,0.0f);
        // Vertex 1
        glNormal3f(0.0f, 0.0f, 1.0f);
        glTexCoord2f(0.0f,1.0f);
        glVertex3f(x,y+h,0.0f);
        // Vertex 2
        glNormal3f(0.0f, 0.0f, 1.0f);
        glTexCoord2f(1.0f,0.0f);
        glVertex3f(x+w,y,0.0f);
        // Vertex 3
        glNormal3f(0.0f, 0.0f, 1.0f);
        glTexCoord2f(1.0f,1.0f);
        glVertex3f(x+w,y+h,0.0f);
    glEnd();  

    pImage->disableTexture();

    glPopMatrix();

    return true;
}


bool 
MyContext::renderCylinder(float fRenderSize[3], float fRenderPos[3], float fRotate[4], unsigned int uiType)
{
    glPushMatrix();
    glLoadIdentity();
    
    MyImage * pTop =  &_3dTex[uiType][INST_TOP];
    MyImage * pSide = &_3dTex[uiType][INST_SIDE]; 
//    pTop->bindTexture();
//    pTop->enableTexture();
    
    // Transformations
    glTranslatef(fRenderPos[0], fRenderPos[1], fRenderPos[2]);
    glRotatef(fRotate[0], fRotate[1], fRotate[2], fRotate[3]);
    glScalef(fRenderSize[0], fRenderSize[1], fRenderSize[2]);

    // Draw the top face
    renderTopCircle(0.0f, 0.0f, -0.5f, 1.0f, 0.0f, 30, 0.0f);
//    pTop->disableTexture();
    
    pSide->bindTexture();
    pSide->enableTexture();
    // Draw the connecting frame
    renderCircle(0.0f, 0.0f, -0.5f, 1.0f, 1.0f, 50, 1.0f);
    
    pSide->disableTexture();

    
    // Draw the bottom plane
    pTop->bindTexture();
    pTop->enableTexture();
    
    renderTopCircle(0.0f, 0.0f, 0.5f, 1.0f, 0.0f, 50, 0.0f);
    
    pTop->disableTexture();
    
    glPopMatrix();
    
    return true;
}


bool 
MyContext::renderCone(float fRenderSize[3], float fRenderPos[3], float fRotate[4], unsigned int uiType)
{
    glPushMatrix();
    glLoadIdentity();
    
    MyImage * pTop =  &_3dTex[uiType][INST_TOP]; 
    pTop->bindTexture();
    pTop->enableTexture();
    
    // Transformations
    glTranslatef(fRenderPos[0], fRenderPos[1], fRenderPos[2]);
    glRotatef(fRotate[0], fRotate[1], fRotate[2], fRotate[3]);
    glScalef(fRenderSize[0], fRenderSize[1], fRenderSize[2]);

    // Draw a cone
    renderCircle(0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 50, 1.0f);
    
    // Draw the flat part beneath!
//    renderCircle(0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 50);
    pTop->disableTexture();
    glPopMatrix();
    return true;
}


bool
MyContext::renderCircle(float fX, float fY, float fZ, float fRad, float fInRad, int iSides, float fZOffset)
{
    // calculate the z for normals (inverse slope)
    float fNorm[] = { 0.0f, 0.0f, 0.0f };
    float fZNorm = 1;
    if(fZOffset != 0)
        fZNorm = (fRad - fInRad)/fZOffset;
        
    glBegin(GL_TRIANGLE_STRIP);
        for(int i=0; i<=iSides; i++)
        {
            /* Find and draw a vertex from the circle */
            float fTheta=(float)i*(2.0f*3.1415f)/(float)iSides;
            // outer point
            glTexCoord2f((float)i/(float)(iSides), 0.0f);
            _norm(cos(fTheta), sin(fTheta), 0.0f, fNorm);
            glNormal3fv(fNorm);
            glVertex3f(fX + fRad*cos(fTheta), fY + fRad*sin(fTheta), fZ);
            // inner point
            glTexCoord2f((float)i/(float)(iSides), 1.0f);
            _norm(cos(fTheta), sin(fTheta), 0.0f, fNorm);
            glNormal3fv(fNorm);
            glVertex3f(fX + fInRad*cos(fTheta), fY + fInRad*sin(fTheta), fZ + fZOffset);
        }
        // last point
        glTexCoord2f(1.0f, 0.0f);
        float fTheta=(float)(iSides+1)*(2.0f*3.1415f)/(float)iSides;
        _norm(cos(fTheta), sin(fTheta), 0.0f, fNorm);
        glNormal3fv(fNorm);
        glVertex3f(fX + fRad*cos(fTheta), fY + fRad*sin(fTheta), fZ);
    glEnd();
    return true;
}


bool
MyContext::renderTopCircle(float fX, float fY, float fZ, float fRad, float fInRad, int iSides, float fZOffset)
{
    // calculate the z for normals (inverse slope)
    float fNorm[] = { 0.0f, 0.0f, 0.0f };
    float fZNorm = 1;
    if(fZOffset != 0)
        fZNorm = (fRad - fInRad)/fZOffset;
    
    glBegin(GL_TRIANGLE_STRIP);
    for(int i=0; i<=iSides; i++)
    {
        /* Find and draw a vertex from the circle */
        float fTheta=(float)i*(2.0f*3.1415f)/(float)iSides;
        // outer point
        glTexCoord2f((float)i/(float)(iSides), 0.0f);
        _norm(0, 0, 1, fNorm);
        glNormal3fv(fNorm);
        glVertex3f(fX + fRad*cos(fTheta), fY + fRad*sin(fTheta), fZ);
        // inner point
        glTexCoord2f((float)i/(float)(iSides), 1.0f);
        _norm(0, 0, 1, fNorm);glNormal3fv(fNorm);
        glVertex3f(fX + fInRad*cos(fTheta), fY + fInRad*sin(fTheta), fZ + fZOffset);
    }
    // last point
    glTexCoord2f(1.0f, 0.0f);
    float fTheta=(float)(iSides+1)*(2.0f*3.1415f)/(float)iSides;
    _norm(0, 0, 1, fNorm);
    glNormal3fv(fNorm);
    glVertex3f(fX + fRad*cos(fTheta), fY + fRad*sin(fTheta), fZ);
    glEnd();
    return true;
}


bool 
MyContext::renderCircle(float fCircle[][8], int iSides)
{
    glBegin(GL_TRIANGLE_STRIP);
    for(int i = 0; i < iSides; i++) {
        glTexCoord2f(fCircle[i][3], fCircle[i][4]);
        glNormal3f(fCircle[i][5], fCircle[i][6], fCircle[i][7]);
        glVertex3f(fCircle[i][0], fCircle[i][1], fCircle[i][2]);
    }
    glEnd();
    return true;
}




// iSides should always be odd--the last inner point is not calculated or stored
// iSides is actually the number of verticies to use--twice the number of sides-1
bool
MyContext::buildCircle(float fCircle[][8], float fX, float fY, float fZ, float fRad, float fInRad, int iSides, float fZOffset)
{
    // calculate the z for normals (inverse slope)
    float fNorm[] = { 0.0f, 0.0f, 0.0f };
    float fZNorm = 1;
    if(fZOffset != 0)
        fZNorm = (fRad - fInRad)/fZOffset;
    
    for(int i=0; (i+1)<(iSides-1); i++)
    {
        /* Find and draw a vertex from the circle */
        float fTheta=(float)i*(2.0f*3.1415f)/(float)(iSides-1);
        // outer point
        // vertex:
        fCircle[i][0] = fX + fRad*cos(fTheta);
        fCircle[i][1] = fY + fRad*sin(fTheta);
        fCircle[i][2] = fZ;
        // tex:
        fCircle[i][3] = (float)i/(float)(iSides-1);
        fCircle[i][4] = 0.0f;
        // normal:
        _norm(fX + fRad*cos(fTheta), fY + fRad*sin(fTheta), fZNorm, fNorm);
        fCircle[i][5] = fNorm[0];
        fCircle[i][6] = fNorm[1];
        fCircle[i][7] = fNorm[2];
        
        // inner point        
        // vertex:
        fCircle[i+1][0] = fX + fInRad*cos(fTheta);
        fCircle[i+1][1] = fY + fInRad*sin(fTheta);
        fCircle[i+1][2] = fZ + fZOffset;
        // tex:
        fCircle[i+1][3] = (float)i/(float)(iSides-1);
        fCircle[i+1][4] = 1.0f;
        // normal:
        _norm(fX + fInRad*cos(fTheta), fY + fInRad*sin(fTheta), fZNorm, fNorm);
        fCircle[i+1][5] = fNorm[0];
        fCircle[i+1][6] = fNorm[1];
        fCircle[i+1][7] = fNorm[2];
    }
    // last point
    float fTheta=(float)iSides*(2.0f*3.1415f)/(float)(iSides);
    // vertex:
    fCircle[iSides-1][0] = fX + fRad*cos(fTheta);
    fCircle[iSides-1][1] = fY + fRad*sin(fTheta);
    fCircle[iSides-1][2] = fZ;
    // tex:
    fCircle[iSides-1][3] = 1.0f;
    fCircle[iSides-1][4] = 1.0f;
    // normal:
    _norm(fX + fRad*cos(fTheta), fY + fRad*sin(fTheta), fZNorm, fNorm);
    fCircle[iSides-1][5] = fNorm[0];
    fCircle[iSides-1][6] = fNorm[1];
    fCircle[iSides-1][7] = fNorm[2];
    return true;
}


// Normalizes a normal :)
bool
MyContext::_norm(float fX, float fY, float fZ, float fNorm[3])
{
    float fDist = sqrt( fX*fX + fY*fY + fZ*fZ );
    fNorm[0] = fX / fDist;
    fNorm[1] = fY / fDist;
    fNorm[2] = fZ / fDist;
    return true;
}


